package org.eremite.corm.party;

import org.eremite.corm.Archetype;
import org.eremite.corm.party.address.*;
import org.eremite.corm.party.relationship.*;
//import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

/**
 * Factory class to speed the writing of unit, integration, and persistence tests.
 * The methods of this class will build whole CORM-Party bean components with
 * random but unique data for names, descriptions, and dates.
 */

public class ComponentFactory {

    private static String num = "0123456789";
    private static String lower = "abcdefghijklmnopqrstuvwxyz";
    private static String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static String value(String type, int a, int b, int c, int d) {
        String array;
        int e;
        if(type.equals("all")){
            array = num + lower + upper;
            e = 62;
        } else if(type.equals("num")){
            array = num;
            e = 10;
        } else if(type.equals("lower")){
            array = lower;
            e = 26;
        } else if(type.equals("upper")){
            array = upper;
            e = 26;
        } else if(type.equals("mixed")){
            array = lower + upper;
            e = 52;
        } else {
            throw new Error("incorrect use of 'value()'.  '" + type + "' is not a supported type");
        }

        String result = "";
        for(int j=0; j<(a + (Math.random() * b)); j++){
            for(int i=0; i<(c + (Math.random() * d)); i++){
                result += array.charAt((int)Math.floor(Math.random() * e));
            }
            result += " ";
        }
        return result;
    }

    private static String value(String a, int b) {
        return value(a, 1, 0, b, 0);
    }

    private static String value() {
        return value("all", 2, 3, 2, 10);
    }

    private static String number(int n) {
        return value("num", 1, 0, n, 0);
    }

    private static String name(){
        return value("mixed", 1, 0, 5, 8);
    }

    private static String desc() {
        return value();
    }

    private Date date() {
        return new Date();
    }



    /* **************************************************************** */
    /* ****** package org.eremite.corm.party.address ****************** */



    private void init(Address result) {
        init((Archetype)result);
        result.setValidFrom(new Date());
        result.setValidTo(new Date());
    }

    private void init(Archetype result) {
        result.setName(value());
        result.setDescription(value());
    }

    public Address address() {
        Address result = new Address();
        init(result);
        return result;
    }

    // Creates its own party and address
    public AssociatedAddress associatedAddress() {
        Set<AssociatedAddress> set = party().getAddresses();
        Iterator<AssociatedAddress> iter = set.iterator();
        return iter.next();
    }

    private AssociatedAddress associatedAddress(Party p, Address a){
        return new AssociatedAddress(p, a, name(), desc());
    }

    public EmailAddress emailAddress() {
        EmailAddress result = new EmailAddress();
        init(result);
        result.setEmailAddress(value());
        return result;
    }

    // many-to-many bidirectional mapping to TelecomAddress
    public GeographicAddress geographicAddress() {
        GeographicAddress result = new GeographicAddress();
        init(result);
        Set<String> lines = result.getAddressLines();
        lines.add(value());
        lines.add(value());
        lines.add(value());
        lines.add(value());
        result.setCity(value());
        result.setLocale(locale());
        result.setRegionOrState(value("lower", 2));

        // map to telecom addresses
        TelecomAddress tel1 = telecomAddress(result);
        TelecomAddress tel2 = telecomAddress(result);
        result.getTelecomAddresses().addAll(Arrays.asList(tel1, tel2));

        return result;
    }

    // many-to-many bidirectional mapping to TelecomAddress
    private GeographicAddress geographicAddress(TelecomAddress ... tels) {
        GeographicAddress result = new GeographicAddress();
        init(result);
        Set<String> lines = result.getAddressLines();
        lines.add(value());
        lines.add(value());
        lines.add(value());
        lines.add(value());
        result.setCity(value());
        result.setLocale(locale());
        result.setRegionOrState(value("lower", 2));

        // map to telecom addresses
        result.getTelecomAddresses().addAll(Arrays.asList(tels));

        return result;
    }

    public ISOCountryCode isoCountryCode() {
        ISOCountryCode result = new ISOCountryCode();
        result.setAlphabeticThreeCharacterCode(value("lower", 3));
        result.setAlphabeticTwoCharacterCode(value("lower", 2));
        result.setNumericCode(number(4));
        result.setOfficialName(value());
        result.setShortName(name());
        return result;
    }

    public Locale locale() {
        Locale result = new Locale();
        init(result);
        return result;
    }

    public TelecomAddress telecomAddress() {
        TelecomAddress result = new TelecomAddress();
        init(result);
        result.setAreaCode(number(3));
        result.setCountryCode(number(1));
        result.setNumber(number(7));
        result.setPhysicalType(value());
        result.setNationalDirectDialingPrefix(number(1));
        result.setExtension(number(2));

        // map the result to two party
        associatedAddress(party(), result);
        associatedAddress(party(), result);

        // set geographic addresses
        GeographicAddress geo1 = geographicAddress(result);
        GeographicAddress geo2 = geographicAddress(result);
        result.getGeographicAddresses().addAll(Arrays.asList(geo1, geo2));

        return result;
    }

    private TelecomAddress telecomAddress(GeographicAddress geogs) {
        TelecomAddress result = new TelecomAddress();
        init(result);
        result.setAreaCode(number(3));
        result.setCountryCode(number(1));
        result.setNumber(number(7));
        result.setPhysicalType(value());
        result.setNationalDirectDialingPrefix(number(1));
        result.setExtension(number(2));

        // map the result to two party objects
        associatedAddress(party(), result);
        associatedAddress(party(), result);

        // set geographic addresses
        result.getGeographicAddresses().addAll(Arrays.asList(geogs));

        return result;
    }

    public WebPageAddress webPageAddress() {
        WebPageAddress result = new WebPageAddress();
        init(result);
        result.setUrl(value());
        return result;
    }



    /* **************************************************************** */
    /* ****** package org.eremite.corm.party.relationship ************* */


    /**
     * Returns an object tree with AssignedResponsibility at its apex.
     * @return AssignedResponsibility, with all fields populated, recursively.
     */
    public AssignedResponsibility assignedResponsibility() {
        return assignedResponsibility(responsibility());
    }

    /**
     * Returns an AssignedResponsibility with three signatures signed by
     * empty party entities.
     * @param resp representing the responsibility to be assigned.
     * @return AssignedResponsibility with three accompanying signatures
     */
    private AssignedResponsibility assignedResponsibility(Responsibility resp) {
        AssignedResponsibility result = new AssignedResponsibility();
        init(result);

        result.setFromDate(date());
        result.setToDate(date());

        result.addPartySignature(
                partySignature(new Party()),
                partySignature(new Party()),
                partySignature(new Party()));

        result.setResponsibility(resp);
        return result;
    }

    public Capability capability() {
        Capability result = new Capability();
        init(result);
        return result;
    }

    /**
     * @return a PartyRelationship with new AssociatedPartyRoles.
     */
    public PartyRelationship partyRelationship() {
        PartyRelationship result = new PartyRelationship();
        init(result);
        result.setPartyRelationshipType(partyRelationshipType());
        result.addParticipant(participant(), participant(), participant());

        return result;
    }

    public PartyRelationshipType partyRelationshipType() {
        return new PartyRelationshipType(name(), desc());
    }

    /**
     * Creates an entity tree beginning with PartyRole.
     * This supplies its own parties, relationships, and
     * all objects associated with them.
     * @return PartyRole and new object tree.
     */
    public PartyRole partyRole() {
        // Make a new PartyRelationship.
        // Return one of its participants
        PartyRole result = null;
        Set<PartyRole> roles = partyRelationship().getParticipants();
        for(PartyRole role : roles) {
            result = role;
        }
        return result;
    }


    private PartyRole participant(){
        PartyRole result = new PartyRole();
        init(result);
        result.setParty(party(result));
        result.setPartyRoleType(partyRoleType());
        return result;
    }

    public PartyRoleType partyRoleType() {
        PartyRoleType result = new PartyRoleType(name(), desc());
        init(result);
        return result;
    }

    public Responsibility responsibility() {
        Responsibility result = new Responsibility();
        init(result);
        return result;
    }



    /* **************************************************************** */
    /* ****** package org.eremite.corm.party ************************** */


    private void init(Party result) {
        init((Archetype)result);

        result.addRegisteredIdentifier(registeredIdentifier());
        result.addCapability(capability(), capability(), capability());
        result.addPreference(preference(), preference(), preference());

        // AssociatedAddress wires itself into the party and address.
        associatedAddress(result, address());
        associatedAddress(result, address());
        associatedAddress(result, address());
    }

    public Organization organization() {
        // create an org
        Organization result = new Organization();
        init(result);
        result.setOrgName(organizationName());

        // create a PartyRole map the result to it
        PartyRole role1 = partyRole();
        PartyRole role2 = partyRole();
        role1.setParty(result);
        role2.setParty(result);
        result.addPartyRole(role1, role2);

        return result;
    }

    public OrganizationName organizationName() {
        OrganizationName result = new OrganizationName();
        init(result);
        result.setValidFrom(date());
        result.setValidTo(date());
        return result;
    }

    public Party party() {
        return partyRole().getParty();
    }

    // must not reference party(), partyRole(), or partyRelationship()
    private Party party(PartyRole role) {
        Party result = new Party();
        init(result);
        result.addPartyRole(role);
        return result;
    }

    public PartySignature partySignature() {
        return partySignature(party());
    }

    private PartySignature partySignature(Party p){
        PartySignature result = new PartySignature(p, value());
        init(result);
        result.setTimeStamp(date());
        return result;
    }

    public Person person() {
        Person result = new Person();
        init(result);
        result.setGender((int)(Math.random() * 3));
        result.setPersonName(personName());
        result.addPseudonym(personName(), personName());

        PartyRole role1 = partyRole();
        PartyRole role2 = partyRole();
        role1.setParty(result);
        role2.setParty(result);
        result.addPartyRole(role1, role2);

        return result;
    }

    public PersonName personName() {
        PersonName result = new PersonName();
        init(result);
        result.setFamilyName(name());
        result.setGivenName(name());
        result.setMiddleName(name());
        result.setPreferredName(name());
        result.setPreferredName(name());
        result.setPrefix(name());
        result.setSuffix(name());
        result.setValidFrom(date());
        result.setValidTo(date());
        return result;
    }

    public Binding preference() {
        Binding result = new Binding(name());
        init(result);
        result.setSalience((int)(Math.random() * 100));
        result.setType(bindingType());
        result.setValue(bindingValue());
        return result;
    }

    public BindingValue bindingValue() {
        BindingValue result = new BindingValue();
        init(result);
        return result;
    }

    public BindingType bindingType() {
        BindingType result = new BindingType(name());
        init(result);
        result.addOption(
                bindingValue(),
                bindingValue(),
                bindingValue());
        return result;
    }

    public RegisteredIdentifier registeredIdentifier() {
        RegisteredIdentifier result = new RegisteredIdentifier();
        init(result);
        result.setContents(value());
        result.setType(name());
        result.setRegistrationAuthority(name());
        result.setValidFrom(date());
        result.setValidTo(date());
        return result;
    }

    // used to test this class.
    public static void main(String[] args) {
        ComponentFactory cf = new ComponentFactory();

        traceln(cf.address());
        traceln(cf.associatedAddress());
        traceln(cf.emailAddress());
        traceln(cf.geographicAddress());
        traceln(cf.isoCountryCode());
        traceln(cf.locale());
        traceln(cf.telecomAddress());
        traceln(cf.webPageAddress());
        traceln(cf.assignedResponsibility());
        traceln(cf.capability());
        traceln(cf.partyRelationship());
        traceln(cf.partyRelationshipType());
        traceln(cf.partyRole());
        traceln(cf.partyRoleType());
        traceln(cf.responsibility());
        traceln(cf.organization());
        traceln(cf.organizationName());
        traceln(cf.party());
        traceln(cf.partySignature());
        traceln(cf.person());
        traceln(cf.personName());
        traceln(cf.preference());
        traceln(cf.bindingValue());
        traceln(cf.bindingType());
        traceln(cf.registeredIdentifier());
    }

    private static void traceln(Object o) {
        System.out.println(o);
    }

}
